﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ScopePropertyObserver.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using System;
    using System.Windows;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;

    public class ScopePropertyObserver
    {
        #region Fields

        private WeakReference _source;
        private FrameworkElement _target;

        #endregion

        public ScopePropertyObserver(FrameworkElement target, ScopeProperty property)
        {
            #region Validate Arguments

            Guard.ArgumentNull("valueProperty", property);
            Guard.ArgumentNull("target", target);

            /* ======================================================================================================
            TODO: Should validate, however, this is not possible as GetMetadata method returns an unexpected result!!
             
            var metaData = valueProperty.GetMetadata(typeof(DependencyObject)) as ScopePropertyMetaData;
            if (metaData == null)
            {
                throw new ArgumentException("The sepecified property has not been registered using ScopePropertyMetaData.");
            }
            ======================================================================================================= */

            #endregion

            Property = property;
            Target = target;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value
        {
            get
            {
                var source = Source;
                return source == null
                    ? Property.DefaultMetadata.DefaultValue
                    : source.GetValue(Property.Property);
            }
        }

        public event EventHandler ValueChanged;

        /// <summary>
        /// Gets the source.
        /// </summary>
        /// <value>The source.</value>
        public DependencyObject Source
        {
            get
            {
                return _source != null ? _source.Target as DependencyObject : null;
            }
            private set
            {
                var oldSource = Source;
                if (oldSource != value)
                {
                    // remove from current source
                    if (oldSource != null)
                    {
                        ScopePropertyBehavior.RemoveObserver(oldSource, this);
                    }

                    // assign, wrapping in weak reference
                    _source = value != null ? new WeakReference(value) : null;

                    // add to new source
                    if (value != null)
                    {
                        ScopePropertyBehavior.AddObserver(value, this);
                        OnValueChanged();
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value property.
        /// </summary>
        /// <value>The value property.</value>
        public ScopeProperty Property { get; private set; }

        /// <summary>
        /// Gets or sets the target.
        /// </summary>
        /// <value>The target.</value>
        public FrameworkElement Target
        {
            get
            {
                return _target;
            }
            set
            {
                if (_target != value)
                {
                    if (_target != null)
                    {
                        _target.Loaded -= TargetLoaded;
                        _target.Unloaded -= TargetUnloaded;
                    }

                    _target = value;
                    if (_target != null)
                    {
                        _target.Loaded += TargetLoaded;
                        _target.Unloaded += TargetUnloaded;
                    }

                    UpdateSource(false);
                }
            }
        }

        private void UpdateSource(bool forceUpdate)
        {
            var valueSource = FindSource(Target);
            if (Source != valueSource)
            {
                Source = valueSource;
            }
            else
            {
                if (forceUpdate)
                {
                    OnValueChanged();
                }
            }
        }

        private void TargetLoaded(object sender, RoutedEventArgs e)
        {
            UpdateSource(true);
        }

        private void TargetUnloaded(object sender, RoutedEventArgs e)
        {
            UpdateSource(true);
        }

        public static DependencyObject FindSource(DependencyObject obj, DependencyProperty property)
        {
            for (var current = obj; current != null; current = GetParent(current))
            {
                var value = current.ReadLocalValue(property);
                if (value != DependencyProperty.UnsetValue)
                {
                    return current;
                }
            }

            return ScopePropertyFallback.Instance;
        }

        public static DependencyObject GetParent(DependencyObject dependencyObject)
        {
            var parent = dependencyObject is UIElement ? VisualTreeHelper.GetParent(dependencyObject) : null;
            if (parent == null)
            {
                var frameworkElement = dependencyObject as FrameworkElement;
                if (frameworkElement != null)
                {
                    return frameworkElement.Parent as Popup;
                }
            }
            return parent;
        }

        private DependencyObject FindSource(DependencyObject obj)
        {
            return FindSource(obj, Property.Property);
        }

        internal void OnValueChanged()
        {
            if (ValueChanged != null)
            {
                ValueChanged(this, EventArgs.Empty);
            }
        }

        internal void InvalidateSource()
        {
            Source = FindSource(_target, Property.Property);
        }
    }
}